package nl.foldervisualizer
/**
 * Date: 3-jun-2008
 * Time: 21:33:47
 */
class FolderVisualizer {
    Map folderSizes = [:]
    private String parent

    /**
     * Constructor
     */
    private FolderVisualizer(Folder folder) {
		this.parent = folder.getCanonicalPath()
                if (this.parent.endsWith(File.separator)) this.parent = this.parent.substring(0, this.parent.length() - 1)

        Map map = [:]

        folder.eachFileRecurse { file ->
			String key = file.getCanonicalPath()
                        if (key.endsWith(File.separator)) key = key.substring(0, key.length() - 1)
            long size = map[key] == null ? 0 : map[key]
            size += file.size()
            map[key] = size
        }

        this.folderSizes = this.filterAndAggregate(map)
    }

    public static FolderVisualizer create(Folder folder) {
        return new FolderVisualizer(folder);
    }

    /**
     * Creates a Map with unique folders under the given parent and as value the total size in bytes
     * of these folders. This map is input for the toVisualize function.
     */
    private Map filterAndAggregate(Map map) {
        Map aggregates = [:]
        map.each { key, value ->
            String directory = key
            computeSize(value, directory, aggregates)
            while (directory != parent) {
				directory = directory.substring(0, directory.lastIndexOf(File.separator))
                computeSize(value, directory, aggregates)
            }
        }
        return aggregates
    }

    private def computeSize(value, directory, Map aggregates) {
        long size = aggregates[directory] == null ? 0 : aggregates[directory]
        size += value
        aggregates[directory] = size
    }

    /**
     * Returns a Map with
     *     keys: folder one-level below the given folder
     *     value: value indicating the relative size of the folder. The sizes of all folders in the map added
     *            up are equal to 100
     *  This map can be used to easily visualize folder sizes.
     */
    public List toVisualize(Folder folder, int ceiling = 10) {
        Map toVisualize = [:]

		long totalSize = 0
		folder.eachFile { file ->
			if (file.isDirectory()) {
				String path = file.getCanonicalPath()
				toVisualize[path] = folderSizes[path]
				totalSize += folderSizes[path]
			} else {
				toVisualize[file.getCanonicalPath()] = file.size()
				totalSize += file.size()
			}
		}

        List data = []
        Set keySet = toVisualize.keySet()
        keySet.collect {
			  data += new FolderItem(it, (double) (toVisualize[it] / totalSize) * 100, toVisualize[it])
        }

        data = data.sort { p1, p2 ->
            if (p1.relativeSize < p2.relativeSize) return 1
            if (p1.relativeSize > p2.relativeSize) return -1
            return 0
        }

        return this.aggregateSmallestEntries(data, ceiling)
    }

    /**
     * Aggregates the smallest entries in the the list starting from (limit - 1) to one
     * entry. The relative size is the relative size of the combined entries.
     *
     * Returns the new List with including the aggregated entries
     */
    private List aggregateSmallestEntries(List data, int ceiling) {
        int limit = (data.size() - ceiling < 0 ? data.size() : ceiling)
        int tooSmallIndex = data.findIndexOf { it.relativeSize < 2}
        if(tooSmallIndex) {
            limit = tooSmallIndex
        }

		if (limit < data.size()) {
                    int index = 0
                    FolderItem aggregatedItems = new FolderItem("Other")
                    data.collect { folderItem ->
                        // Create a new FolderItem entry for all entries where the index is >= than the specified limit
                        if (index >= limit - 1) {
                            aggregatedItems += folderItem
                        }
                        index++
                    }

                    data[limit - 1] = aggregatedItems
		}

        return data.subList(0, limit)
    }
}